﻿// Copyright (c) Microsoft Corporation. All rights reserved. See License.txt in the project root for license information.
#include "pch.h"
#include <DirectXColors.h>
#include <numeric>
#include <random>
#include <shcore.h> // CreateStreamOverRandomAccessStream

#include "BasicShapes.h"
#include "BasicLoader.h"
#include "D2DBitmapHelper.h"
#include "..\Common\DirectXHelper.h" // For ThrowIfFailed and ReadDataAsync
#include "ExceptionPolicyFactory.h"
#include "DataModel\ForecastManager.h"
#include "GeometryHelper.h"
#include "MessageDialog.h"
#include "SceneRenderer.h"
#include "SunLocator.h"
#include "ThumbnailManager.h"
#include "UserSettings.h"
#include "VertexFormats.h"

using namespace Weathr;

using namespace concurrency;
using namespace DirectX;
using namespace Microsoft::WRL;
using namespace Platform;
using namespace std;
using namespace Windows::Foundation;
using namespace Windows::Globalization;
using namespace Windows::System;
using namespace Windows::UI::Core;

const utility::datetime::interval_type CheckinInterval = 15 * 60; // how often to update weather info, in seconds

const double UpdateSunPositionIntervalSeconds = 5.0 * 60.0;       // how often to update the sun's position, in seconds
const double UpdateThumbnailIntervalSeconds = 0.33;               // how often to check the queue for thumbnail updates

const UINT ThumbnailWidth = 310u;      // thumbnail image width
const UINT ThumbnailHeight = 310u;     // thumbnail image height

#ifdef _M_ARM
const uint32_t NumStars = 1000;         // number of stars to render
#else
const uint32_t NumStars = 4000;
#endif

const unsigned int UserPreferencesTimerIntervalMs = 250;    // how often to update user preferences, in ms.

// Loads vertex and pixel shaders from files and instantiates the cube geometry.
SceneRenderer::SceneRenderer(const std::shared_ptr<DeviceResources>& deviceResources)
    : m_exceptionPolicy(ExceptionPolicyFactory::GetCurrentPolicy())
    , m_deviceResources(deviceResources)
    , m_controller(LonLatController::Current)
    , m_loadingComplete(false)
    , m_sunPositionTimer(ref new BasicTimer())
    , m_thumbnailUpdateTimer(ref new BasicTimer())
    , m_renderWeatherEffects(UserSettings::Current->ShowWeatherEffects)
    , m_renderStarfield(UserSettings::Current->ShowStarfield)
    , m_calendar(ref new Calendar)
{
    // Create a call object that updates the current user settings.
    m_updateUserPreferencesCall = make_unique<concurrency::call<timer_signal>>([this](timer_signal signal)
    {
        (void) signal;
        m_renderWeatherEffects = UserSettings::Current->ShowWeatherEffects;
        m_renderStarfield = UserSettings::Current->ShowStarfield;
    });
    // Invoke the call on a regular interval.
    m_updateUserPreferencesTimer = make_unique<concurrency::timer<timer_signal>>(UserPreferencesTimerIntervalMs, timer_signal(), m_updateUserPreferencesCall.get(), true);
    m_updateUserPreferencesTimer->start();

    CreateDeviceDependentResources();
    CreateWindowSizeDependentResources();
}

void SceneRenderer::CreateDeviceDependentResources()
{
    // Load each set of resources asynchronously.
    vector<task<void>> tasks;
    tasks.emplace_back(LoadEarthResourcesAsync());
    tasks.emplace_back(LoadStarfieldResourcesAsync());
    tasks.emplace_back(LoadCloudResourcesAsync());

    when_all(begin(tasks), end(tasks)).then([this](task<void> previousTask)
    {
        try
        {
            previousTask.get();
        }
        catch (...)
        {
            // TODO: I haven't investigated a comprehensive list of what could go wrong.
            // For now, just catch all exceptions and show a dialog box.
            String^ message = "Something went wrong while initializing the app";
            String^ title = "Sorry";
            MessageDialog::ShowAcceptDialogAsync(message, title);

            // TODO: Open question: what should be done at the point where the app can't initialize?
        }

        // Once all resources are loaded, the objects are ready to be rendered.
        m_loadingComplete = true;
    });
}

// Initializes view parameters when the window size changes.
void SceneRenderer::CreateWindowSizeDependentResources()
{
    Size outputSize = m_deviceResources->GetOutputSize();
    float aspectRatio = outputSize.Width / outputSize.Height;
    float fovAngleY = 70.0f * XM_PI / 180.0f;

    // This is a simple example of change that can be made when the app is in
    // portrait or snapped view.
    if (aspectRatio < 1.0f)
    {
        fovAngleY *= 2.0f;
    }

    // Note that the OrientationTransform3D matrix is post-multiplied here
    // in order to correctly orient the scene to match the display orientation.
    // This post-multiplication step is required for any draw calls that are
    // made to the swap chain render target. For draw calls to other targets,
    // this transform should not be applied.

    // This sample makes use of a right-handed coordinate system using row-major matrices.
    XMMATRIX perspectiveMatrix = XMMatrixPerspectiveFovRH(
        fovAngleY,
        aspectRatio,
        0.01f,
        100.0f
        );

    XMFLOAT4X4 orientation = m_deviceResources->GetOrientationTransform3D();

    XMMATRIX orientationMatrix = XMLoadFloat4x4(&orientation);

    XMStoreFloat4x4(
        &m_nativeProjection,
        XMMatrixTranspose(perspectiveMatrix)
        );

    XMStoreFloat4x4(
        &m_earthResources.m_vsConstantBufferData.projection,
        XMMatrixTranspose(perspectiveMatrix * orientationMatrix)
        );

    m_viewport = m_deviceResources->GetScreenViewport();
    DXGI_MODE_ROTATION rotation;
    DX::ThrowIfFailed(
        m_deviceResources->GetSwapChain()->GetRotation(&rotation)
        );
    switch (rotation)
    {
    case DXGI_MODE_ROTATION_UNSPECIFIED:
    case DXGI_MODE_ROTATION_IDENTITY:
    case DXGI_MODE_ROTATION_ROTATE180:
    default:
        break;
    case DXGI_MODE_ROTATION_ROTATE90:
    case DXGI_MODE_ROTATION_ROTATE270:
        swap(m_viewport.Width, m_viewport.Height);
        break;
    }

    // Adjust the viewport to account for the current resolution scale.
    // This enables us to properly project from 3D to 2D.
    float resolutionScale = 1.0f;
    switch (Windows::Graphics::Display::DisplayInformation::GetForCurrentView()->ResolutionScale)
    {
    default:
    case Windows::Graphics::Display::ResolutionScale::Scale120Percent: // not currently used
    case Windows::Graphics::Display::ResolutionScale::Scale150Percent: // currently applies only to Windows Phone 8
    case Windows::Graphics::Display::ResolutionScale::Scale160Percent: // currently applies only to Windows Phone 8
    case Windows::Graphics::Display::ResolutionScale::Scale225Percent: // not currently used
        assert(false); // assert in Debug builds and fall through.
    case Windows::Graphics::Display::ResolutionScale::Scale100Percent:
        resolutionScale = 1.0f;
        break;
    case Windows::Graphics::Display::ResolutionScale::Scale140Percent:
        resolutionScale = 1.4f;
        break;
    case Windows::Graphics::Display::ResolutionScale::Scale180Percent:
        resolutionScale = 1.8f;
        break;
    }
    m_viewport.Width /= resolutionScale;
    m_viewport.Height /= resolutionScale;
}

void SceneRenderer::ReleaseDeviceDependentResources()
{
    m_loadingComplete = false;

    m_earthResources.m_inputLayout.Reset();
    m_earthResources.m_vertexBuffer.Reset();
    m_earthResources.m_indexBuffer.Reset();
    m_earthResources.m_vertexShader.Reset();
    m_earthResources.m_pixelShader.Reset();
    m_earthResources.m_texture.Reset();
    m_earthResources.m_textureSRV.Reset();
    m_earthResources.m_sampler.Reset();
    m_earthResources.m_normals.Reset();
    m_earthResources.m_normalSRV.Reset();
    m_earthResources.m_normalSampler.Reset();
    m_earthResources.m_vsConstantBuffer.Reset();
    m_earthResources.m_psConstantBuffer.Reset();

    m_starfieldResources.m_inputLayout.Reset();
    m_starfieldResources.m_vertexBuffer.Reset();
    m_starfieldResources.m_indexBuffer.Reset();
    m_starfieldResources.m_vertexShader.Reset();
    m_starfieldResources.m_pixelShader.Reset();
    m_starfieldResources.m_texture.Reset();
    m_starfieldResources.m_textureSRV.Reset();
    m_starfieldResources.m_sampler.Reset();
    m_starfieldResources.m_vsConstantBuffer.Reset();
    m_starfieldResources.m_blendState.Reset();

    m_cloudResources.m_inputLayout.Reset();
    m_cloudResources.m_vertexBuffer.Reset();
    m_cloudResources.m_indexBuffer.Reset();
    m_cloudResources.m_vertexShader.Reset();
    m_cloudResources.m_pixelShader.Reset();
    m_cloudResources.m_staticMap.Reset();
    m_cloudResources.m_staticMapSRV.Reset();
    m_cloudResources.m_sampler.Reset();
    m_cloudResources.m_psConstantBuffer.Reset();
    m_cloudResources.m_blendState.Reset();
    m_cloudResources.m_depthStencilState.Reset();
}

// Called once per frame, rotates the cube and calculates the model and view matrices.
void SceneRenderer::Update(DX::StepTimer const& timer)
{
    (void) timer;

    auto forecastManager = ForecastManager::Current;

    // Prepare to pass the view matrix, and updated model matrix, to the shader.
    XMFLOAT3 temp;
    temp = m_controller->get_Position();
    XMVECTOR eye = XMVectorSet(temp.x, temp.y, temp.z, 0);
    temp = m_controller->get_LookPoint();
    XMVECTOR at = XMVectorSet(temp.x, temp.y, temp.z, 0);
    XMVECTOR up = XMVectorSet(0, 1, 0, 0);
    CXMMATRIX view = XMMatrixLookAtRH(eye, at, up);
    XMStoreFloat4x4(&m_earthResources.m_vsConstantBufferData.view, XMMatrixTranspose(view));

    CXMMATRIX model = XMMatrixRotationY(XM_PI);
    XMStoreFloat4x4(&m_earthResources.m_vsConstantBufferData.model, XMMatrixTranspose(model));

    // Update the model matrix based on the time.
    m_controller->Update();

    const auto now = utility::datetime::utc_now();

    CXMMATRIX projection = XMMatrixTranspose(XMLoadFloat4x4(&m_nativeProjection));

    // These will hold the largest minimum date and the smallest maximum date across all visible forecasts.
    // We will use these values to update the forecast manager's calendar range.
    m_calendar->SetToMax();
    const DateTime MaxDate = m_calendar->GetDateTime();
    DateTime maxDate = MaxDate;
    m_calendar->SetToMin();
    const DateTime MinDate = m_calendar->GetDateTime();
    DateTime minDate = MinDate;

    for (auto group : forecastManager->ForecastGroups)
    {
        bool groupVisible = false; // flags whether any forecast in the group is visible.
        for (auto forecast : group->Items)
        {
            // Check visibilty.
            auto sphereCoordinates = forecast->SphereCoordinates;
            auto angle = XMVectorGetX(XMVector3Dot(XMLoadFloat4(&sphereCoordinates), eye));
            forecast->IsVisible = angle > XM_PIDIV4;

            // Update position if forecast is visible. Also check in for the latest forcast if it's time to.
            if (forecast->IsVisible)
            {
                // Position
                const XMFLOAT2 screenLocation = forecast->Latlon.ToScreenCoordinates(1.0f, m_viewport, XMMatrixIdentity(), view, projection);
                forecast->ScreenLocationLeft = static_cast<int32>(screenLocation.x);
                forecast->ScreenLocationTop = static_cast<int32>(screenLocation.y);

                // Forecast
                const utility::datetime::interval_type elapsed = now - forecast->LastCheckin;
                if (elapsed >= CheckinInterval)
                {
                    forecastManager->CheckinAsync(forecast, now).then(ObserveException<void>(m_exceptionPolicy));
                }

                // Date range
                if (forecast->MinDate.UniversalTime > minDate.UniversalTime)
                {
                    minDate = forecast->MinDate;
                }
                if (forecast->MaxDate.UniversalTime < maxDate.UniversalTime)
                {
                    maxDate = forecast->MaxDate;
                }

                groupVisible = true;
            }
        }

        // Update group visibiltiy and position, if needed.
        group->IsVisible = groupVisible;
        if (groupVisible && group->HasMultipleItems)
        {
            const auto pt = group->Center;
            const auto r = group->ComputeRadius();
            group->ScreenLocationLeft = static_cast<int32>(pt.X - r);
            group->ScreenLocationTop = static_cast<int32>(pt.Y + r);
        }
    }

    // Update calendar range to refect the date range available for the visible locations.
    if (minDate.UniversalTime != MinDate.UniversalTime && maxDate.UniversalTime != MaxDate.UniversalTime)
    {
        forecastManager->UpdateCalendarRange(minDate, maxDate);
    }

    // Update sun position as needed.
    m_sunPositionTimer->Update();
    static bool firstSunUpdate = true;
    if (firstSunUpdate || m_sunPositionTimer->Total >= UpdateSunPositionIntervalSeconds)
    {
        firstSunUpdate = false;
        m_sunPositionTimer->Reset();
        XMStoreFloat4(&m_earthResources.m_psConstantBufferData.light, SunLocator::GetLocation());
    }

    // Update pin as needed.
    auto pin = Pin::Current;
    if (pin != nullptr && pin->IsDirty)
    {
        pin->IsDirty = false;

        LatLon latlon;
        if (PinHitTest(static_cast<float>(pin->X + pin->Width / 2), static_cast<float>(pin->Y + pin->Height), latlon))
        {
            pin->GetLocatorAgent().QueueLocationRequest(latlon.Latitude, latlon.Longitude);
        }
    }
}

// Renders one frame using the vertex and pixel shaders.
void SceneRenderer::Render()
{
    // Loading is asynchronous. Only draw geometry after it's loaded.
    if (!m_loadingComplete)
    {
        return;
    }

    // Periodically check to see if a thumbnail image needs to be rendered.
    m_thumbnailUpdateTimer->Update();
    static bool firstThumbnailUpdate = true;
    if (firstThumbnailUpdate || m_thumbnailUpdateTimer->Total > UpdateThumbnailIntervalSeconds)
    {
        firstThumbnailUpdate = false;
        m_thumbnailUpdateTimer->Reset();

        if (ForecastManager::Current->PopThumbnailUpdateQueue(m_thumbnailUpdate))
        {
            auto forecast = get<0>(m_thumbnailUpdate);
            auto thumbnailStream = get<1>(m_thumbnailUpdate);
            auto completionEvent = get<2>(m_thumbnailUpdate);

            // Set up scene constant buffers.

            // Camera is slightly above location.
            const XMVECTOR eye = forecast->Latlon.ToSphereCoordinates(1.15f);
            const XMVECTOR at = XMVectorZero();
            const XMVECTOR up = XMVectorSet(0, 1, 0, 0);
            CXMMATRIX view = XMMatrixLookAtRH(eye, at, up);
            XMFLOAT4X4 viewMatrix;
            XMStoreFloat4x4(&viewMatrix, XMMatrixTranspose(view));

            const Size nativeOutputSize = m_deviceResources->GetOutputSize();
            Size outputSize(1024, 768);
            if (nativeOutputSize.Height > nativeOutputSize.Width)
            {
                swap(outputSize.Width, outputSize.Height);
            }

            float aspectRatio = outputSize.Width / outputSize.Height;
            float fovAngleY = 70.0f * XM_PI / 180.0f;
            CXMMATRIX projection = XMMatrixPerspectiveFovRH(
                fovAngleY,
                aspectRatio,
                0.01f,
                100.0f
                );
            XMFLOAT4X4 projectionMatrix;
            XMStoreFloat4x4(&projectionMatrix, XMMatrixTranspose(projection));

            const ModelViewProjectionConstantBuffer tempVCB = m_earthResources.m_vsConstantBufferData;
            const ModelViewProjectionConstantBuffer mvpCB =
            {
                m_earthResources.m_vsConstantBufferData.model,
                viewMatrix,
                projectionMatrix
            };

            // Light is above location.
            const LightConstantBuffer tempPCB = m_earthResources.m_psConstantBufferData;
            XMFLOAT4 light;
            XMStoreFloat4(&light, forecast->Latlon.ToSphereCoordinates(2.0f));
            const LightConstantBuffer lightCB = { light };

            // Render scene to offscreen image.
            m_earthResources.m_vsConstantBufferData = mvpCB;
            m_earthResources.m_psConstantBufferData = lightCB;
            RenderEarth();
            m_earthResources.m_vsConstantBufferData = tempVCB;
            m_earthResources.m_psConstantBufferData = tempPCB;

            // Write frame buffer to stream.
            ComPtr<IStream> imageStream;
            DX::ThrowIfFailed(
                CreateStreamOverRandomAccessStream(thumbnailStream, IID_PPV_ARGS(&imageStream))
                );
            SaveBitmapToStream(m_deviceResources->GetD2DDevice(), m_deviceResources->GetWicImagingFactory(), m_deviceResources->GetD2DTargetBitmap(), imageStream.Get(), ThumbnailManager::GetThumbnailImageFormat());
            // Seek to stream start.
            LARGE_INTEGER li;
            memset(&li, 0, sizeof (li));
            DX::ThrowIfFailed(imageStream->Seek(li, STREAM_SEEK_SET, nullptr));

            // Load image back to resize it.
            ComPtr<ID2D1Bitmap1> bitmap = LoadBitmapFromStream(
                m_deviceResources->GetD2DDeviceContext(),
                m_deviceResources->GetWicImagingFactory(),
                imageStream.Get(),
                ThumbnailWidth,
                ThumbnailHeight);

            // Now seek back to start and save it back again.
            DX::ThrowIfFailed(imageStream->Seek(li, STREAM_SEEK_SET, nullptr));
            SaveBitmapToStream(m_deviceResources->GetD2DDevice(), m_deviceResources->GetWicImagingFactory(), bitmap.Get(), imageStream.Get(), ThumbnailManager::GetThumbnailImageFormat());
            imageStream->Commit(STGC_DEFAULT);

            // Set the task completion event.
            completionEvent.set();

            // Clear the back buffer and depth stencil view for rendering main scene.
            auto context = m_deviceResources->GetD3DDeviceContext();
            context->ClearRenderTargetView(m_deviceResources->GetBackBufferRenderTargetView(), Colors::Black);
            context->ClearDepthStencilView(m_deviceResources->GetDepthStencilView(), D3D11_CLEAR_DEPTH | D3D11_CLEAR_STENCIL, 1.0f, 0);
        }
    }

    // Render main scene.
    RenderEarth();
    if (m_renderStarfield) // based on user preference
    {
        RenderStarfield();
    }
    if (m_renderWeatherEffects) // based on user preference
    {
        RenderClouds();
    }
}

// Loads resources for rendering the Earth
task<void> SceneRenderer::LoadEarthResourcesAsync()
{
    // Load shaders asynchronously.
    auto loadVSTask = DX::ReadDataAsync(L"BumpVertexShader.cso");
    auto loadPSTask = DX::ReadDataAsync(L"BumpPixelShader.cso");

    // After the vertex shader file is loaded, create the shader and input layout.
    auto createVSTask = loadVSTask.then([this](const std::vector<byte>& fileData) {
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateVertexShader(
            &fileData[0],
            fileData.size(),
            nullptr,
            &m_earthResources.m_vertexShader
            )
            );

        static const D3D11_INPUT_ELEMENT_DESC tangentVertexLayoutDesc [] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TANGENT", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TANGENT", 1, DXGI_FORMAT_R32G32B32_FLOAT, 0, 32, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        };

        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateInputLayout(
            tangentVertexLayoutDesc,
            ARRAYSIZE(tangentVertexLayoutDesc),
            &fileData[0],
            fileData.size(),
            &m_earthResources.m_inputLayout
            )
            );
    });

    // After the pixel shader file is loaded, create the shader and constant buffer.
    auto createPSTask = loadPSTask.then([this](const std::vector<byte>& fileData) {
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreatePixelShader(
            &fileData[0],
            fileData.size(),
            nullptr,
            &m_earthResources.m_pixelShader
            )
            );

        CD3D11_BUFFER_DESC vsConstantBufferDesc(sizeof(ModelViewProjectionConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateBuffer(
            &vsConstantBufferDesc,
            nullptr,
            &m_earthResources.m_vsConstantBuffer
            )
            );

        CD3D11_BUFFER_DESC psConstantBufferDesc(sizeof(LightConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateBuffer(
            &psConstantBufferDesc,
            nullptr,
            &m_earthResources.m_psConstantBuffer
            )
            );
    });

    // Once both shaders are loaded, load the mesh and textures.
    return (createPSTask && createVSTask).then([this]()
    {
        auto device = m_deviceResources->GetD3DDevice();

        // Create the mesh.
        auto shapes = ref new BasicShapes(device);

        shapes->CreateTangentSphere(
            &m_earthResources.m_vertexBuffer,
            &m_earthResources.m_indexBuffer,
            nullptr,
            &m_earthResources.m_indexCount
            );

        // Create the textures.
        auto loader = ref new BasicLoader(device);

        loader->LoadTexture(
            LR"(Content\texture.dds)",
            &m_earthResources.m_texture,
            &m_earthResources.m_textureSRV
            );

        loader->LoadTexture(
            LR"(Content\normals.dds)",
            &m_earthResources.m_normals,
            &m_earthResources.m_normalSRV
            );

        // Create the sampler.
        D3D11_SAMPLER_DESC samplerDesc;
        ZeroMemory(&samplerDesc, sizeof(D3D11_SAMPLER_DESC));
        samplerDesc.Filter = D3D11_FILTER_ANISOTROPIC;
        samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.MipLODBias = 0.0f;
        samplerDesc.MaxAnisotropy = device->GetFeatureLevel() >= D3D_FEATURE_LEVEL_9_2 ? 4 : 2;
        samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
        samplerDesc.BorderColor[0] = 0.0f;
        samplerDesc.BorderColor[1] = 0.0f;
        samplerDesc.BorderColor[2] = 0.0f;
        samplerDesc.BorderColor[3] = 0.0f;
        // Allow use of all mip levels.
        samplerDesc.MinLOD = 0;
        samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

        DX::ThrowIfFailed(
            device->CreateSamplerState(
            &samplerDesc,
            &m_earthResources.m_sampler)
            );

        // create the normal map sampler
        ZeroMemory(&samplerDesc, sizeof(D3D11_SAMPLER_DESC));
        samplerDesc.Filter = D3D11_FILTER_ANISOTROPIC;
        samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.MipLODBias = 0.0f;
        samplerDesc.MaxAnisotropy = device->GetFeatureLevel() >= D3D_FEATURE_LEVEL_9_2 ? 4 : 2;
        samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
        samplerDesc.BorderColor[0] = 0.0f;
        samplerDesc.BorderColor[1] = 0.0f;
        samplerDesc.BorderColor[2] = 0.0f;
        samplerDesc.BorderColor[3] = 0.0f;
        // Allow use of all mip levels.
        samplerDesc.MinLOD = 0;
        samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

        DX::ThrowIfFailed(
            device->CreateSamplerState(
            &samplerDesc,
            &m_earthResources.m_normalSampler)
            );
    });
}

// Loads resources for rendering the starfield
task<void> SceneRenderer::LoadStarfieldResourcesAsync()
{
    // Load shaders asynchronously.
    auto loadVSTask = DX::ReadDataAsync(L"StarVertexShader.cso");
    auto loadPSTask = DX::ReadDataAsync(L"StarPixelShader.cso");

    // After the vertex shader file is loaded, create the shader and input layout.
    auto createVSTask = loadVSTask.then([this](const std::vector<byte>& fileData) {
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateVertexShader(
            &fileData[0],
            fileData.size(),
            nullptr,
            &m_starfieldResources.m_vertexShader
            )
            );

        static const D3D11_INPUT_ELEMENT_DESC vertexLayoutDesc [] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "COLOR", 0, DXGI_FORMAT_R32G32B32A32_FLOAT, 0, 20, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        };

        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateInputLayout(
            vertexLayoutDesc,
            ARRAYSIZE(vertexLayoutDesc),
            &fileData[0],
            fileData.size(),
            &m_starfieldResources.m_inputLayout
            )
            );
    });

    // After the pixel shader file is loaded, create the shader and constant buffer.
    auto createPSTask = loadPSTask.then([this](const std::vector<byte>& fileData) {
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreatePixelShader(
            &fileData[0],
            fileData.size(),
            nullptr,
            &m_starfieldResources.m_pixelShader
            )
            );

        CD3D11_BUFFER_DESC vsConstantBufferDesc(sizeof(ModelViewProjectionCameraConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateBuffer(
            &vsConstantBufferDesc,
            nullptr,
            &m_starfieldResources.m_vsConstantBuffer
            )
            );
    });

    // Once both shaders are loaded, load the mesh and texture.
    return (createPSTask && createVSTask).then([this]()
    {
        auto device = m_deviceResources->GetD3DDevice();

        // Create the mesh.
        const uint32_t numVertices = NumStars * 6;
        unique_ptr<StarVertex []> sphereVertices(new StarVertex[numVertices]);
        StarVertex* ptr = sphereVertices.get();

        const array<float, 2u> signs = { -1.0f, +1.0f };
        for (uint32_t i = 0; i < NumStars; ++i)
        {
            const XMFLOAT3 v(
                (signs[rand() % 2])*(rand() % 100),
                (signs[rand() % 2])*(rand() % 100),
                (signs[rand() % 2])*(10 + rand() % 20)
                );

            XMFLOAT4 color(1, 1, 1, 1);
            // Occasionally randomize the color, but keep it bright.
            if (rand() % 25 == 0)
            {
                color.x = (128 + rand() % 128) / 256.0f;
                color.y = (128 + rand() % 128) / 256.0f;
                color.z = (128 + rand() % 128) / 256.0f;
            }
            const float particleSize = 0.1f;

            StarVertex verts [] = {
                // Bottom left.
                { { v.x - particleSize, v.y - particleSize, v.z }, { 0.0f, 1.0f }, color },
                // Top left.
                { { v.x - particleSize, v.y + particleSize, v.z }, { 0.0f, 0.0f }, color },
                // Bottom right.
                { { v.x + particleSize, v.y - particleSize, v.z }, { 1.0f, 1.0f }, color },
                // Bottom right.
                { { v.x + particleSize, v.y - particleSize, v.z }, { 1.0f, 1.0f }, color },
                // Top left.
                { { v.x - particleSize, v.y + particleSize, v.z }, { 0.0f, 0.0f }, color },
                // Top right.
                { { v.x + particleSize, v.y + particleSize, v.z }, { 1.0f, 0.0f }, color }
            };

            // Wind the triangles the opposite way if the quad is behind us.
            if (verts[0].pos.z > 0)
            {
                std::swap(verts[1], verts[2]);
                std::swap(verts[4], verts[5]);
            }

            for (auto && vert : verts)
            {
                *ptr = vert;
                ptr++;
            }
        }

        const uint32_t numIndices = numVertices;
        unique_ptr<unsigned short []> sphereIndices(new unsigned short[numIndices]);
        for (uint32_t index = 0; index < numIndices; ++index)
        {
            sphereIndices[index] = static_cast<unsigned short>(index);
        }

        CreateVertexBuffer<StarVertex>(
            numVertices,
            sphereVertices.get(),
            &m_starfieldResources.m_vertexBuffer,
            m_deviceResources->GetD3DDevice()
            );

        CreateIndexBuffer<unsigned short>(
            numIndices,
            sphereIndices.get(),
            &m_starfieldResources.m_indexBuffer,
            m_deviceResources->GetD3DDevice()
            );
        m_starfieldResources.m_indexCount = numIndices;

        // Create the textures.
        auto loader = ref new BasicLoader(device);

        loader->LoadTexture(
            LR"(Content\star.dds)",
            &m_starfieldResources.m_texture,
            &m_starfieldResources.m_textureSRV
            );

        // Create the sampler.
        D3D11_SAMPLER_DESC samplerDesc;
        ZeroMemory(&samplerDesc, sizeof(D3D11_SAMPLER_DESC));
        samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.MipLODBias = 0.0f;
        samplerDesc.MaxAnisotropy = m_deviceResources->GetDeviceFeatureLevel() >= D3D_FEATURE_LEVEL_9_2 ? 4 : 2;
        samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
        samplerDesc.BorderColor[0] = 0.0f;
        samplerDesc.BorderColor[1] = 0.0f;
        samplerDesc.BorderColor[2] = 0.0f;
        samplerDesc.BorderColor[3] = 0.0f;
        // allow use of all mip levels
        samplerDesc.MinLOD = 0;
        samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateSamplerState(
            &samplerDesc,
            &m_starfieldResources.m_sampler)
            );

        D3D11_BLEND_DESC blendStateDesc;
        ZeroMemory(&blendStateDesc, sizeof(D3D11_BLEND_DESC));
        blendStateDesc.AlphaToCoverageEnable = FALSE;
        blendStateDesc.IndependentBlendEnable = FALSE;
        blendStateDesc.RenderTarget[0].BlendEnable = TRUE;
        blendStateDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_ONE;
        blendStateDesc.RenderTarget[0].DestBlend = D3D11_BLEND_ONE;
        blendStateDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
        blendStateDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO;
        blendStateDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
        blendStateDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
        blendStateDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;

        m_deviceResources->GetD3DDevice()->CreateBlendState(&blendStateDesc, &m_starfieldResources.m_blendState);
    });
}

// Loads resources for rendering clouds
task<void> SceneRenderer::LoadCloudResourcesAsync()
{
    m_cloudResources.m_timer = ref new BasicTimer();

    // Load shaders asynchronously.
    auto loadVSTask = DX::ReadDataAsync(L"CloudVertexShader.cso");
    auto loadPSTask = DX::ReadDataAsync(L"CloudPixelShader.cso");

    // After the vertex shader file is loaded, create the shader and input layout.
    auto createVSTask = loadVSTask.then([this](const std::vector<byte>& fileData) {
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateVertexShader(
            &fileData[0],
            fileData.size(),
            nullptr,
            &m_cloudResources.m_vertexShader
            )
            );

        static const D3D11_INPUT_ELEMENT_DESC vertexLayoutDesc [] =
        {
            { "POSITION", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 0, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "NORMAL", 0, DXGI_FORMAT_R32G32B32_FLOAT, 0, 12, D3D11_INPUT_PER_VERTEX_DATA, 0 },
            { "TEXCOORD", 0, DXGI_FORMAT_R32G32_FLOAT, 0, 24, D3D11_INPUT_PER_VERTEX_DATA, 0 },
        };

        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateInputLayout(
            vertexLayoutDesc,
            ARRAYSIZE(vertexLayoutDesc),
            &fileData[0],
            fileData.size(),
            &m_cloudResources.m_inputLayout
            )
            );
    });

    // After the pixel shader file is loaded, create the shader and constant buffer.
    auto createPSTask = loadPSTask.then([this](const std::vector<byte>& fileData) {
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreatePixelShader(
            &fileData[0],
            fileData.size(),
            nullptr,
            &m_cloudResources.m_pixelShader
            )
            );

        CD3D11_BUFFER_DESC psConstantBufferDesc(sizeof(CloudParametersConstantBuffer), D3D11_BIND_CONSTANT_BUFFER);
        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateBuffer(
            &psConstantBufferDesc,
            nullptr,
            &m_cloudResources.m_psConstantBuffer
            )
            );
    });

    // Once both shaders are loaded, load the mesh and texture.
    return (createPSTask && createVSTask).then([this]()
    {
        auto device = m_deviceResources->GetD3DDevice();

        // Create the mesh.
        auto shapes = ref new BasicShapes(device);

        shapes->CreateSphere(
            &m_cloudResources.m_vertexBuffer,
            &m_cloudResources.m_indexBuffer,
            nullptr,
            &m_cloudResources.m_indexCount
            );

        mt19937 rand(static_cast<unsigned int>(chrono::system_clock::now().time_since_epoch().count()));
        const size_t resolution = 128;
        vector<array<byte, 4>> noisyColors(resolution * resolution);
        generate(begin(noisyColors), end(noisyColors), [&rand]()->array<byte, 4>
        {
            const byte c = rand() % 256;
            return { c, c, c, c };
        });

        D3D11_SUBRESOURCE_DATA initialData;
        ZeroMemory(&initialData, sizeof(initialData));
        initialData.pSysMem = reinterpret_cast<void*>(&noisyColors[0]);
        initialData.SysMemPitch = resolution * 4;
        initialData.SysMemSlicePitch = 0;

        D3D11_TEXTURE2D_DESC textureDesc;
        ZeroMemory(&textureDesc, sizeof(textureDesc));
        textureDesc.Width = resolution;
        textureDesc.Height = resolution;
        textureDesc.MipLevels = 1;
        textureDesc.ArraySize = 1;
        textureDesc.Format = DXGI_FORMAT_R8G8B8A8_UNORM;
        textureDesc.SampleDesc.Count = 1;
        textureDesc.Usage = D3D11_USAGE_DEFAULT;
        textureDesc.BindFlags = D3D11_BIND_SHADER_RESOURCE;
        textureDesc.CPUAccessFlags = 0;
        textureDesc.MiscFlags = 0;

        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateTexture2D(
            &textureDesc,
            &initialData,
            &m_cloudResources.m_staticMap
            )
            );

        D3D11_SHADER_RESOURCE_VIEW_DESC shaderResourceViewDesc;
        shaderResourceViewDesc.Format = textureDesc.Format;
        shaderResourceViewDesc.ViewDimension = D3D11_SRV_DIMENSION_TEXTURE2D;
        shaderResourceViewDesc.Texture2D.MostDetailedMip = 0;
        shaderResourceViewDesc.Texture2D.MipLevels = 1;

        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateShaderResourceView(
            m_cloudResources.m_staticMap.Get(),
            &shaderResourceViewDesc,
            &m_cloudResources.m_staticMapSRV
            )
            );

        // Create the sampler.
        D3D11_SAMPLER_DESC samplerDesc;
        ZeroMemory(&samplerDesc, sizeof(D3D11_SAMPLER_DESC));
        samplerDesc.Filter = D3D11_FILTER_MIN_MAG_MIP_LINEAR;
        samplerDesc.AddressU = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressV = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.AddressW = D3D11_TEXTURE_ADDRESS_WRAP;
        samplerDesc.MipLODBias = 0.0f;
        samplerDesc.MaxAnisotropy = m_deviceResources->GetDeviceFeatureLevel() >= D3D_FEATURE_LEVEL_9_2 ? 4 : 2;
        samplerDesc.ComparisonFunc = D3D11_COMPARISON_NEVER;
        samplerDesc.BorderColor[0] = 0.0f;
        samplerDesc.BorderColor[1] = 0.0f;
        samplerDesc.BorderColor[2] = 0.0f;
        samplerDesc.BorderColor[3] = 0.0f;
        // allow use of all mip levels
        samplerDesc.MinLOD = 0;
        samplerDesc.MaxLOD = D3D11_FLOAT32_MAX;

        DX::ThrowIfFailed(
            m_deviceResources->GetD3DDevice()->CreateSamplerState(
            &samplerDesc,
            &m_cloudResources.m_sampler)
            );

        D3D11_BLEND_DESC blendStateDesc;
        ZeroMemory(&blendStateDesc, sizeof(D3D11_BLEND_DESC));
        blendStateDesc.AlphaToCoverageEnable = FALSE;
        blendStateDesc.IndependentBlendEnable = FALSE;
        blendStateDesc.RenderTarget[0].BlendEnable = TRUE;
        blendStateDesc.RenderTarget[0].SrcBlend = D3D11_BLEND_SRC_ALPHA;
        blendStateDesc.RenderTarget[0].DestBlend = D3D11_BLEND_INV_SRC_ALPHA;
        blendStateDesc.RenderTarget[0].BlendOp = D3D11_BLEND_OP_ADD;
        blendStateDesc.RenderTarget[0].SrcBlendAlpha = D3D11_BLEND_ZERO;
        blendStateDesc.RenderTarget[0].DestBlendAlpha = D3D11_BLEND_ZERO;
        blendStateDesc.RenderTarget[0].BlendOpAlpha = D3D11_BLEND_OP_ADD;
        blendStateDesc.RenderTarget[0].RenderTargetWriteMask = D3D11_COLOR_WRITE_ENABLE_ALL;

        m_deviceResources->GetD3DDevice()->CreateBlendState(&blendStateDesc, &m_cloudResources.m_blendState);

        // Create depth stencil state that disables depth testing.
        CD3D11_DEPTH_STENCIL_DESC desc(CD3D11_DEFAULT{});
        desc.DepthEnable = FALSE;
        m_deviceResources->GetD3DDevice()->CreateDepthStencilState(&desc, &m_cloudResources.m_depthStencilState);
    });
}

// Renders the Earth
void SceneRenderer::RenderEarth()
{
    auto context = m_deviceResources->GetD3DDeviceContext();

    UINT stride = sizeof(TangentVertex);
    UINT offset = 0;

    // Prepare the constant buffers to send them to the Graphics device.
    context->UpdateSubresource(
        m_earthResources.m_vsConstantBuffer.Get(),
        0,
        nullptr,
        &m_earthResources.m_vsConstantBufferData,
        0,
        0
        );

    context->UpdateSubresource(
        m_earthResources.m_psConstantBuffer.Get(),
        0,
        nullptr,
        &m_earthResources.m_psConstantBufferData,
        0,
        0
        );

    context->IASetInputLayout(m_earthResources.m_inputLayout.Get());

    // set the vertex and index buffers, and specify the way they define geometry
    context->IASetVertexBuffers(
        0,                              // starting at the first vertex buffer slot
        1,                              // set one vertex buffer binding
        m_earthResources.m_vertexBuffer.GetAddressOf(),
        &stride,                        // specify the size in bytes of a single vertex
        &offset                         // specify the base vertex in the buffer
        );

    // set the index buffer
    context->IASetIndexBuffer(
        m_earthResources.m_indexBuffer.Get(),
        DXGI_FORMAT_R16_UINT,   // unsigned short index format
        0                       // specify the base index in the buffer
        );

    // specify the way the vertex and index buffers define geometry
    context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // set the vertex shader stage state
    context->VSSetShader(
        m_earthResources.m_vertexShader.Get(),
        nullptr,                // don't use shader linkage
        0                       // don't use shader linkage
        );

    context->VSSetConstantBuffers(
        0,                              // starting at the first constant buffer slot
        1,                              // set one constant buffer binding
        m_earthResources.m_vsConstantBuffer.GetAddressOf()
        );

    // set the pixel shader stage state
    context->PSSetShader(
        m_earthResources.m_pixelShader.Get(),
        nullptr,                // don't use shader linkage
        0                       // don't use shader linkage
        );

    context->PSSetConstantBuffers(
        1,                              // starting at the second constant buffer slot
        1,                              // set one constant buffer binding
        m_earthResources.m_psConstantBuffer.GetAddressOf()
        );

    // first put the two surfaces into an array
    ID3D11ShaderResourceView* shaderResourceViews [] = {
        m_earthResources.m_textureSRV.Get(),
        m_earthResources.m_normalSRV.Get()
    };

    // then bind them to the device
    context->PSSetShaderResources(
        0,                          // starting at the first shader resource slot
        2,                          // bind 2 shader resources
        shaderResourceViews
        );

    // then put the two samplers into an array
    ID3D11SamplerState* samplers[] = {
        m_earthResources.m_sampler.Get(),
        m_earthResources.m_normalSampler.Get()
    };

    // and bind them to the device
    context->PSSetSamplers(
        0,                          // starting at the first sampler slot
        2,                          // bind 2 shader resources
        samplers
        );

    // draw the mesh
    context->DrawIndexed(
        m_earthResources.m_indexCount,   // draw all created vertices
        0,              // starting with the first vertex
        0               // and the first index
        );
}

// Renders the starfield
void SceneRenderer::RenderStarfield()
{
    auto context = m_deviceResources->GetD3DDeviceContext();

    UINT stride = sizeof(StarVertex);
    UINT offset = 0;

    // Set model, view, and projection matrices.
    XMStoreFloat4x4(&m_starfieldResources.m_vsConstantBufferData.model, XMMatrixIdentity());
    m_starfieldResources.m_vsConstantBufferData.view = m_earthResources.m_vsConstantBufferData.view;
    // Prevent stars from participating in zoom effect.
    m_starfieldResources.m_vsConstantBufferData.view._14 = 0;
    m_starfieldResources.m_vsConstantBufferData.view._24 = 0;
    m_starfieldResources.m_vsConstantBufferData.view._34 = 0;

    m_starfieldResources.m_vsConstantBufferData.projection = m_earthResources.m_vsConstantBufferData.projection;
    XMFLOAT3 temp;
    temp = m_controller->get_Position();
    m_starfieldResources.m_vsConstantBufferData.camera.x = temp.x;
    m_starfieldResources.m_vsConstantBufferData.camera.y = temp.y;
    m_starfieldResources.m_vsConstantBufferData.camera.z = temp.z;

    // update the constant buffer with the new data
    context->UpdateSubresource(
        m_starfieldResources.m_vsConstantBuffer.Get(),
        0,
        nullptr,
        &m_starfieldResources.m_vsConstantBufferData,
        0,
        0
        );

    context->IASetInputLayout(m_starfieldResources.m_inputLayout.Get());

    // set the vertex and index buffers, and specify the way they define geometry
    context->IASetVertexBuffers(
        0,                              // starting at the first vertex buffer slot
        1,                              // set one vertex buffer binding
        m_starfieldResources.m_vertexBuffer.GetAddressOf(),
        &stride,                        // specify the size in bytes of a single vertex
        &offset                         // specify the base vertex in the buffer
        );

    // set the index buffer
    context->IASetIndexBuffer(
        m_starfieldResources.m_indexBuffer.Get(),
        DXGI_FORMAT_R16_UINT,   // unsigned short index format
        0                       // specify the base index in the buffer
        );

    // specify the way the vertex and index buffers define geometry
    context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // set the vertex shader stage state
    context->VSSetShader(
        m_starfieldResources.m_vertexShader.Get(),
        nullptr,                // don't use shader linkage
        0                       // don't use shader linkage
        );

    context->VSSetConstantBuffers(
        0,                              // starting at the first constant buffer slot
        1,                              // set one constant buffer binding
        m_starfieldResources.m_vsConstantBuffer.GetAddressOf()
        );

    // set the pixel shader stage state
    context->PSSetShader(
        m_starfieldResources.m_pixelShader.Get(),
        nullptr,                // don't use shader linkage
        0                       // don't use shader linkage
        );

    // first put the two surfaces into an array
    ID3D11ShaderResourceView* shaderResourceViews[] = {
        m_starfieldResources.m_textureSRV.Get()
    };

    // then bind them to the device
    context->PSSetShaderResources(
        0,                          // starting at the first shader resource slot
        1,                          // bind 1 shader resource
        shaderResourceViews
        );

    // then put the two samplers into an array
    ID3D11SamplerState* samplers[] = {
        m_starfieldResources.m_sampler.Get()
    };

    // and bind them to the device
    context->PSSetSamplers(
        0,                          // starting at the first sampler slot
        1,                          // bind 1 shader resource
        samplers
        );

    // get previous blend state
    ID3D11BlendState* previousBlendState;
    float previousBlendFactor[4];
    UINT previousSampleMask;
    context->OMGetBlendState(&previousBlendState, previousBlendFactor, &previousSampleMask);

    context->OMSetBlendState(m_starfieldResources.m_blendState.Get(), nullptr, 0xFFFFFFFF);

    // draw the mesh
    context->DrawIndexed(
        m_starfieldResources.m_indexCount,   // draw all created vertices
        0,              // starting with the first vertex
        0               // and the first index
        );

    // restore previous blend state
    context->OMSetBlendState(previousBlendState, previousBlendFactor, previousSampleMask);
}

// Renders the clouds
void SceneRenderer::RenderClouds()
{
    auto forecasts = ForecastManager::Current->GetVisibleForecasts();
    if (forecasts.size() == 0)
    {
        return;
    }

    m_cloudResources.m_timer->Update();

    auto context = m_deviceResources->GetD3DDeviceContext();

    ID3D11DepthStencilState* prevDepthStencilState = nullptr;
    UINT stencilRef;
    context->OMGetDepthStencilState(&prevDepthStencilState, &stencilRef);
    context->OMSetDepthStencilState(m_cloudResources.m_depthStencilState.Get(), stencilRef);

    ID3D11BlendState* previousBlendState;
    float previousBlendFactor[4];
    UINT previousSampleMask;
    context->OMGetBlendState(&previousBlendState, previousBlendFactor, &previousSampleMask);
    context->OMSetBlendState(m_cloudResources.m_blendState.Get(), nullptr, 0xFFFFFFFF);

    UINT stride = sizeof(BasicVertex);
    UINT offset = 0;

    context->IASetInputLayout(m_cloudResources.m_inputLayout.Get());

    // set the vertex and index buffers, and specify the way they define geometry
    context->IASetVertexBuffers(
        0,                              // starting at the first vertex buffer slot
        1,                              // set one vertex buffer binding
        m_cloudResources.m_vertexBuffer.GetAddressOf(),
        &stride,                        // specify the size in bytes of a single vertex
        &offset                         // specify the base vertex in the buffer
        );

    // set the index buffer
    context->IASetIndexBuffer(
        m_cloudResources.m_indexBuffer.Get(),
        DXGI_FORMAT_R16_UINT,   // unsigned short index format
        0                       // specify the base index in the buffer
        );

    // specify the way the vertex and index buffers define geometry
    context->IASetPrimitiveTopology(D3D11_PRIMITIVE_TOPOLOGY_TRIANGLELIST);

    // set the vertex shader stage state
    context->VSSetShader(
        m_cloudResources.m_vertexShader.Get(),
        nullptr,                // don't use shader linkage
        0                       // don't use shader linkage
        );

    // update the constant buffer with the new data
    context->UpdateSubresource(
        m_earthResources.m_vsConstantBuffer.Get(),
        0,
        nullptr,
        &m_earthResources.m_vsConstantBufferData,
        0,
        0
        );

    context->VSSetConstantBuffers(
        0,                              // starting at the first constant buffer slot
        1,                              // set one constant buffer binding
        m_earthResources.m_vsConstantBuffer.GetAddressOf()
        );

    // set the pixel shader stage state
    context->PSSetShader(
        m_cloudResources.m_pixelShader.Get(),
        nullptr,                // don't use shader linkage
        0                       // don't use shader linkage
        );

    // first put the two surfaces into an array
    ID3D11ShaderResourceView* shaderResourceViews[] = {
        m_cloudResources.m_staticMapSRV.Get()
    };

    // then bind them to the device
    context->PSSetShaderResources(
        0,                          // starting at the first shader resource slot
        1,                          // bind 1 shader resource
        shaderResourceViews
        );

    // then put the two samplers into an array
    ID3D11SamplerState* samplers[] = {
        m_cloudResources.m_sampler.Get()
    };

    // and bind them to the device
    context->PSSetSamplers(
        0,                          // starting at the first sampler slot
        1,                          // bind 1 shader resource
        samplers
        );

    // Render clouds at each location
    for (auto forecast : forecasts)
    {
        auto weatherData = forecast->Current;
        if (weatherData == nullptr)
        {
            continue;
        }

        // You can tweak these values even further to create more dramatic effects.
        switch (weatherData->WeatherType)
        {
        case WeatherType::Clear:
        default:
            continue;
        case WeatherType::Cloudy:
            m_cloudResources.m_psConstantBufferData.overcast = 1.1f;
            m_cloudResources.m_psConstantBufferData.colorFactor = 1.0f;
            break;
        case WeatherType::Drizzle:
            m_cloudResources.m_psConstantBufferData.overcast = 1.2f;
            m_cloudResources.m_psConstantBufferData.colorFactor = 0.9f;
            break;
        case WeatherType::Haze:
            m_cloudResources.m_psConstantBufferData.overcast = 0.9f;
            m_cloudResources.m_psConstantBufferData.colorFactor = 1.0f;
            break;
        case WeatherType::MostlyCloudy:
            m_cloudResources.m_psConstantBufferData.overcast = 1.5f;
            m_cloudResources.m_psConstantBufferData.colorFactor = 1.0f;
            break;
        case WeatherType::SlightDrizzle:
            m_cloudResources.m_psConstantBufferData.overcast = 1.3f;
            m_cloudResources.m_psConstantBufferData.colorFactor = 0.9f;
            break;
        case WeatherType::Snow:
            m_cloudResources.m_psConstantBufferData.overcast = 1.4f;
            m_cloudResources.m_psConstantBufferData.colorFactor = 1.0f;
            break;
        case WeatherType::Thunderstorms:
            m_cloudResources.m_psConstantBufferData.overcast = 1.4f;
            m_cloudResources.m_psConstantBufferData.colorFactor = 0.75f;
            break;
        }

        const float windDirRadians = XMConvertToRadians(static_cast<float>(weatherData->WindDirectionDegree));
        m_cloudResources.m_psConstantBufferData.move.x = sinf(windDirRadians);
        m_cloudResources.m_psConstantBufferData.move.y = -cosf(windDirRadians);

        m_cloudResources.m_psConstantBufferData.speed = m_cloudResources.m_timer->Total / 2000.0f * weatherData->WindSpeedMph;

        m_cloudResources.m_psConstantBufferData.centerNormal = forecast->SphereCoordinates;

        // update the constant buffer with the new data
        context->UpdateSubresource(
            m_cloudResources.m_psConstantBuffer.Get(),
            0,
            nullptr,
            &m_cloudResources.m_psConstantBufferData,
            0,
            0
            );

        ID3D11Buffer* buffers[] = {
            m_earthResources.m_psConstantBuffer.Get(),
            m_cloudResources.m_psConstantBuffer.Get()
        };

        context->PSSetConstantBuffers(
            1,                              // starting at the first constant buffer slot
            2,                              // set one constant buffer binding
            buffers
            );

        // draw the mesh
        context->DrawIndexed(
            m_cloudResources.m_indexCount,   // draw all created vertices
            0,              // starting with the first vertex
            0               // and the first index
            );
    }

    // restore previous blend state
    context->OMSetBlendState(previousBlendState, previousBlendFactor, previousSampleMask);
    context->OMSetDepthStencilState(prevDepthStencilState, stencilRef);
}

// Finds the intersection between a ray and a sphere.
// Adapted from Real Time Rendering, 741
bool XM_CALLCONV RaySphereIntersect(FXMVECTOR o, FXMVECTOR d, FXMVECTOR c, float r, float& t, XMVECTOR& p)
{
    auto r2 = r*r;
    auto l = c - o;
    auto s = XMVectorGetX(XMVector3Dot(l, d));
    auto l2 = XMVectorGetX(XMVector3Dot(l, l));
    if (s < 0 && l2 > r2)
    {
        return false;
    }
    auto m2 = l2 - (s*s);
    if (m2 > r2)
    {
        return false;
    }
    auto q = sqrtf(r2 - m2);
    if (l2 > r2)
    {
        t = s - q;
    }
    else
    {
        t = s + q;
    }
    p = o + (t * d);
    return true;
}

// Determines whether the provided 2D coordinates pass through the unit sphere.
bool SceneRenderer::PinHitTest(float x, float y, LatLon& result)
{
    // Build model, view, projection matrices.
    XMFLOAT3 temp;
    temp = m_controller->get_Position();
    XMVECTOR eye = XMVectorSet(temp.x, temp.y, temp.z, 0);
    temp = m_controller->get_LookPoint();
    XMVECTOR at = XMVectorSet(temp.x, temp.y, temp.z, 0);
    XMVECTOR up = XMVectorSet(0, 1, 0, 0);

    CXMMATRIX view = XMMatrixLookAtRH(eye, at, up);
    CXMMATRIX model = XMMatrixIdentity();
    CXMMATRIX projection = XMMatrixTranspose(XMLoadFloat4x4(&m_nativeProjection));

    // Unproject (x,y) from 2D to 3D space.
    const XMVECTOR v1 = XMVector3Unproject(
        XMVectorSet(x, y, 0.0f, 0.0f),
        m_viewport.TopLeftX,
        m_viewport.TopLeftY,
        m_viewport.Width,
        m_viewport.Height,
        m_viewport.MinDepth,
        m_viewport.MaxDepth,
        projection,
        view,
        model
        );

    const XMVECTOR v2 = XMVector3Unproject(
        XMVectorSet(x, y, 1, 0),
        m_viewport.TopLeftX,
        m_viewport.TopLeftY,
        m_viewport.Width,
        m_viewport.Height,
        m_viewport.MinDepth,
        m_viewport.MaxDepth,
        projection,
        view,
        model
        );

    // Cast a ray through the unit sphere to determine if the point passes through it.
    float32 t;
    XMVECTOR p;
    if (!RaySphereIntersect(v1, XMVector3Normalize(v2 - v1), XMVectorZero(), 1.0f, t, p))
    {
        return false;
    }

    result = LatLon::FromSphereCoordinates(p);
    return true;
}
